package com.uhfsat;

import com.mechanatrix.input.JoystickState;
import com.mechanatrix.input.ThirdPersonCamera;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.JoyAxisTrigger;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Quad;
import com.jme3.system.AppSettings;
import com.jme3.terrain.geomipmap.TerrainGrid;
import com.jme3.terrain.geomipmap.TerrainGridListener;
import com.jme3.terrain.geomipmap.TerrainGridLodControl;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.grid.ImageTileLoader;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.texture.Texture;
import com.jme3.util.TangentBinormalGenerator;
import java.util.logging.Logger;

/**
 * test
 *
 * @author normenhansen
 */
public class Main extends SimpleApplication implements AnalogListener, ActionListener {

	private static final Logger logger = Logger.getLogger(Main.class.getName());
	private JoystickState joystick;
	private BulletAppState bulletAppState;
	private BetterCharacterControl physicsControl;
	private Spatial model;
	private Node characterNode;
	private ThirdPersonCamera cameraControl;
	private Geometry ground;

	public static void main(String[] args) {
		Main app = new Main();
		AppSettings settings = new AppSettings(true);
		settings.setUseJoysticks(true);
		app.setSettings(settings);
		app.start();
	}

	private void initPlayer() {

		// Create a node for the character model
		characterNode = new Node("character node");
		characterNode.setLocalTranslation(new Vector3f(4, 5, 2));
		characterNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);

		// Add a character control to the node so we can add other things and
		// control the model rotation
		physicsControl = new BetterCharacterControl(4.373348f / 2f, 5.182257f, 500.0f);
		characterNode.addControl(physicsControl);
		getPhysicsSpace().add(physicsControl);

		// Load model, attach to character node
		//model = assetManager.loadModel("Models/pennisetum/pennisetum.obj");
		model = assetManager.loadModel("Models/saguaro/saguaro.obj");
		//Material material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
		//model.setMaterial(material);
		//model = assetManager.loadModel("Models/untitled.obj");
		model.scale(0.01f);
		//model.setLocalTranslation(0f, 5.182257f / 2f, 0f);
		model.setCullHint(Spatial.CullHint.Never);
		model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
		characterNode.attachChild(model);
		// Add character node to the rootNode
		rootNode.attachChild(characterNode);

		/*
		 CollisionShape colShape = CollisionShapeFactory.createDynamicMeshShape(model);
		 physicsControl = new UHFRigidBody(colShape, 500);
		 getPhysicsSpace().add(physicsControl);
		 //physicsControl = new UHFSatcomControl(colShape, 500);
		 //physicsControl = new PlayerControl(colShape, 500);
		 //physicsControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
		 //model.addControl(physicsControl);
		 //rootNode.attachChild(model);
		 //bulletAppState.getPhysicsSpace().add(physicsControl);
		 //rootNode.attachChild(model);
		 */
	}

	private void initCamera() {
		flyCam.setEnabled(false);
		cameraControl = new ThirdPersonCamera(cam, model, inputManager);
		cameraControl.setSmoothMotion(true);
		cameraControl.setLookAtOffset(Vector3f.UNIT_Y.mult(3));
		cameraControl.setMaxVerticalRotation(FastMath.HALF_PI);
	}

	private void initLighting() {
		DirectionalLight sun = new DirectionalLight();
		sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
		rootNode.addLight(sun);

		AmbientLight light = new AmbientLight();
		light.setColor(ColorRGBA.LightGray);
		//light.setColor(ColorRGBA.White.mult(0.5f));
		rootNode.addLight(light);

		/*
		 DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 3);
		 dlsf.setLight(sun);
		 dlsf.setLambda(0.55f);
		 dlsf.setShadowIntensity(0.6f);
		 dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
		 FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
		 fpp.addFilter(dlsf);
		 viewPort.addProcessor(fpp);
		 */
	}

	private void initTerrain() {
		/*
		 // init a physics test scene
		 PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace());
		 PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace());
		 // build world
		 //Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
		 //material.setTexture("ColorMap", assetManager.loadTexture("Textures/concrete_01_dif.tga"));
		 Material material = assetManager.loadMaterial("MatDefs/metal_plates_18.j3m");
		 //immovable sphere with mesh collision shape
		 Sphere sphere = new Sphere(64, 64, 20);
		 floorGeom = new Geometry("Sphere", sphere);
		 floorGeom.setMaterial(material);
		 floorGeom.setLocalTranslation(30, -15, 30);
		 floorGeom.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0));
		 rootNode.attachChild(floorGeom);
		 getPhysicsSpace().add(floorGeom);
		 */
		// TODO Get phong colors and shininess for metals 3 & 10.
		// TODO Get inverted normal maps for floorGeom 1, metal 3 & plaster 3.
		// TODO Make sure we're using the moveRight normal axis inversion.
		Material mat_ground = assetManager.loadMaterial("MatDefs/concrete_01.j3m");		// white concrete
		assetManager.loadMaterial("MatDefs/ground_01.j3m");		// desert ground
		assetManager.loadMaterial("MatDefs/metal_plates_02.j3m");	// metal grooves
		assetManager.loadMaterial("MatDefs/metal_plates_03.j3m");	// brushed steel
		assetManager.loadMaterial("MatDefs/metal_plates_04.j3m");	// metal slats
		assetManager.loadMaterial("MatDefs/metal_plates_05.j3m");	// metal vents
		assetManager.loadMaterial("MatDefs/metal_plates_06.j3m");	// rough metal
		assetManager.loadMaterial("MatDefs/metal_plates_08.j3m");	// hardened machinery
		assetManager.loadMaterial("MatDefs/metal_plates_09.j3m");	// corrugated metal
		assetManager.loadMaterial("MatDefs/metal_plates_10.j3m");	// industrial floor
		assetManager.loadMaterial("MatDefs/metal_plates_12.j3m");	// square plate
		assetManager.loadMaterial("MatDefs/metal_plates_13.j3m");	// new metal
		assetManager.loadMaterial("MatDefs/metal_plates_18.j3m");	// irregular plate
		assetManager.loadMaterial("MatDefs/metal_plates_19.j3m");	// industrial machinery
		assetManager.loadMaterial("MatDefs/mosaic_floor_01.j3m");	// mosaic tiles
		assetManager.loadMaterial("MatDefs/plaster_01.j3m");		// yellow plaster
		assetManager.loadMaterial("MatDefs/plaster_03.j3m");		// gray plaster
		assetManager.loadMaterial("MatDefs/plaster_06.j3m");		// fuscha plaster
		/*
		// Create a box with a texture
		Box box01 = new Box(2.5f, 2.5f, 2.5f);
		TangentBinormalGenerator.generate(box01);
		Spatial spat01 = new Geometry("Box", box01);
		spat01.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
		spat01.setLocalTranslation(-30.0f, -2.5f, 0.0f);
		spat01.rotate(0f, (float) Math.PI / 3f, (float) Math.PI / 3f);
		Material mat01 = assetManager.loadMaterial("MatDefs/mosaic_floor_01.j3m");
		spat01.setMaterial(mat01);
		rootNode.attachChild(spat01);
		*/
		Geometry floorGeom = new Geometry("ground", new Quad(50, 50));
		floorGeom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
		floorGeom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
		floorGeom.setLocalTranslation(-25, -10, 25);
		floorGeom.setMaterial(mat_ground);
		floorGeom.addControl(new RigidBodyControl(0));
		rootNode.attachChild(floorGeom);
		getPhysicsSpace().addAll(floorGeom);


	}

	private void buildTerrain() {
		// TERRAIN TEXTURE material
		Material mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md");

		Texture reg1 = this.assetManager.loadTexture("Textures/plaster_06_dif.tga"); //fuscha
		reg1.setWrap(Texture.WrapMode.Repeat);
		mat_terrain.setTexture("region1ColorMap", reg1);
		mat_terrain.setVector3("region1", new Vector3f(88, 200, 8));

		// DIRT texture
		Texture reg2 = this.assetManager.loadTexture("Textures/concrete_01_dif.tga");
		reg2.setWrap(Texture.WrapMode.Repeat);
		mat_terrain.setTexture("region2ColorMap", reg2);
		mat_terrain.setVector3("region2", new Vector3f(0, 90, 8));

		// ROCK texture
		Texture reg3 = this.assetManager.loadTexture("Textures/plaster_03_dif.tga");
		reg3.setWrap(Texture.WrapMode.Repeat);
		mat_terrain.setTexture("region3ColorMap", reg3);
		mat_terrain.setVector3("region3", new Vector3f(198, 260, 8));

		Texture reg4 = this.assetManager.loadTexture("Textures/metal_plates_09_dif.tga");
		mat_terrain.setTexture("region4ColorMap", reg4);
		mat_terrain.setVector3("region4", new Vector3f(198, 260, 8));

		Texture slope = this.assetManager.loadTexture("Textures/metal_plates_09_dif.tga");
		mat_terrain.setTexture("slopeColorMap", slope);
		mat_terrain.setFloat("slopeTileFactor", 32);

		mat_terrain.setFloat("terrainSize", 129);
		ImageTileLoader grid = new ImageTileLoader("Scenes/TerrainMountains/terrain", "png", assetManager);

		final TerrainGrid terrain = new TerrainGrid("terrain", 65, 257, grid);

		terrain.setMaterial(mat_terrain);
		terrain.setLocalTranslation(0, -200, 0);
		//terrain.setLocalScale(1f, 0.9f, 1f);

		//RigidBodyControl rbcontrol = new RigidBodyControl(0);
		//terrain.addControl(rbcontrol);
		rootNode.attachChild(terrain);
		getPhysicsSpace().addAll(terrain);
		//getPhysicsSpace().add(rbcontrol);

		terrain.addListener(new TerrainGridListener() {
			@Override
			public void gridMoved(Vector3f newCenter) {
			}

			@Override
			public void tileAttached(Vector3f cell, TerrainQuad quad) {
				while (quad.getControl(RigidBodyControl.class) != null) {
					quad.removeControl(RigidBodyControl.class);
				}
				quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
				getPhysicsSpace().add(quad);
			}

			@Override
			public void tileDetached(Vector3f cell, TerrainQuad quad) {
				if (quad.getControl(RigidBodyControl.class) != null) {
					getPhysicsSpace().remove(quad);
					quad.removeControl(RigidBodyControl.class);
				}
			}
		});

		TerrainLodControl control = new TerrainGridLodControl(terrain, getCamera());
		control.setLodCalculator(new DistanceLodCalculator(65, 2.7f)); // patch size, and a multiplier
		terrain.addControl(control);

		this.viewPort.setBackgroundColor(new ColorRGBA(1.0f, 0.8f, 0.7f, 1f));

	}

	private void initInput() {
		// TODO Add controller buttons
		inputManager.addMapping(UHFControl.MOVE_UP,
				new KeyTrigger(KeyInput.KEY_UP),
				new JoyAxisTrigger(0, 2, true)); //up on moveLeft stick
		inputManager.addMapping(UHFControl.MOVE_DOWN,
				new KeyTrigger(KeyInput.KEY_DOWN),
				new JoyAxisTrigger(0, 2, false)); //down on moveLeft stick
		inputManager.addMapping(UHFControl.MOVE_LEFT,
				new KeyTrigger(KeyInput.KEY_LEFT),
				new JoyAxisTrigger(0, 3, true)); //left on moveLeft stick
		inputManager.addMapping(UHFControl.MOVE_RIGHT,
				new KeyTrigger(KeyInput.KEY_RIGHT),
				new JoyAxisTrigger(0, 3, false)); //right on moveLeft stick
		inputManager.addMapping(UHFControl.SELECT,
				new KeyTrigger(KeyInput.KEY_TAB));
		inputManager.addMapping(UHFControl.START,
				new KeyTrigger(KeyInput.KEY_RETURN));
		inputManager.addMapping(UHFControl.LOOK_UP,
				new JoyAxisTrigger(0, 0, true)); //up on right stick
		inputManager.addMapping(UHFControl.LOOK_DOWN,
				new JoyAxisTrigger(0, 0, false)); //down on right stick
		inputManager.addMapping(UHFControl.LOOK_LEFT,
				new JoyAxisTrigger(0, 1, true)); //left on right stick
		inputManager.addMapping(UHFControl.LOOK_RIGHT,
				new JoyAxisTrigger(0, 1, false)); //right on right stick
		inputManager.addMapping(UHFControl.A,
				new KeyTrigger(KeyInput.KEY_A));
		inputManager.addMapping(UHFControl.B,
				new KeyTrigger(KeyInput.KEY_B));
		inputManager.addMapping(UHFControl.X,
				new KeyTrigger(KeyInput.KEY_X));
		inputManager.addMapping(UHFControl.Y,
				new KeyTrigger(KeyInput.KEY_Y));
		inputManager.addListener(this,
				UHFControl.MOVE_UP,
				UHFControl.MOVE_DOWN,
				UHFControl.MOVE_RIGHT,
				UHFControl.MOVE_LEFT,
				UHFControl.SELECT,
				UHFControl.START,
				UHFControl.LOOK_UP,
				UHFControl.LOOK_DOWN,
				UHFControl.LOOK_LEFT,
				UHFControl.LOOK_RIGHT,
				UHFControl.A,
				UHFControl.B,
				UHFControl.X,
				UHFControl.Y);
	}

	private void initAudio() {
	}

	@Override
	public void simpleInitApp() {

		//initInput();

		// activate physics
		bulletAppState = new BulletAppState();
		bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
		stateManager.attach(bulletAppState);
		//bulletAppState.setDebugEnabled(true);
		bulletAppState.getPhysicsSpace().setAccuracy(1f / 30f);

		initLighting();
		initTerrain();
		//buildTerrain();

		initPlayer();
		initCamera();
		initAudio();

		joystick = new JoystickState();
		inputManager.addRawInputListener(joystick);
	}

	@Override
	public void simpleUpdate(float tpf) {
		super.simpleUpdate(tpf);
		//fpsText.setText("Touch da floorGeom = " + physicsControl.isOnGround());
		//listener.setLocation(cam.getLocation());
		//listener.setRotation(cam.getRotation());
	}

	@Override
	public void simpleRender(RenderManager rm) {
		//TODO: add render code
	}

	@Override
	public void onAnalog(String name, float value, float tpf) {
		switch (name) {
			case UHFControl.MOVE_LEFT:
				//physicsControl.moveLeft(value, tpf);
				//model.move(-5 * tpf, 0, 0);
				break;
			case UHFControl.MOVE_RIGHT:
				//physicsControl.moveRight(value, tpf);
				//model.move(5 * tpf, 0, 0);
				break;
			case UHFControl.MOVE_UP:
				//physicsControl.moveForward(value, tpf);
				//model.move(0, 0, -5 * tpf);
				break;
			case UHFControl.MOVE_DOWN:
				//physicsControl.moveBackward(value, tpf);
				//model.move(0, 0, 5 * tpf);
				break;
		}
	}

	@Override
	public void onAction(String name, boolean isPressed, float tpf) {
		switch (name) {
			case UHFControl.MOVE_LEFT:
				//physicsControl.moveLeft(isPressed, tpf);
				break;
			case UHFControl.MOVE_RIGHT:
				//physicsControl.moveRight(isPressed, tpf);
				break;
			case UHFControl.MOVE_UP:
				//physicsControl.moveForward(isPressed, tpf);
				break;
			case UHFControl.MOVE_DOWN:
				//physicsControl.moveBackward(isPressed, tpf);
				break;
			case UHFControl.SELECT:
				//physicsControl.select(isPressed, tpf);
				break;
			case UHFControl.START:
				//physicsControl.start(isPressed, tpf);
				break;
			case UHFControl.A:
				//physicsControl.a(isPressed, tpf);
				break;
			case UHFControl.B:
				//physicsControl.b(isPressed, tpf);
				break;
			case UHFControl.X:
				//physicsControl.x(isPressed, tpf);
				break;
			case UHFControl.Y:
				//physicsControl.y(isPressed, tpf);
				break;
		}
	}

	private PhysicsSpace getPhysicsSpace() {
		return bulletAppState.getPhysicsSpace();
	}
}
